Merge branch 'master' into dropbox-watch-agent

Conflicts:
app/assets/stylesheets/application.css.scss.erb
app/helpers/application_helper.rb

Guilherme J. Tramontina преди 9 години
родител
ревизия
d40319458b

+ 5 - 0
.env.example

@@ -135,6 +135,11 @@ ENABLE_INSECURE_AGENTS=false
135 135
 # "on the minute") is disallowed to prevent abuse of service.
136 136
 ENABLE_SECOND_PRECISION_SCHEDULE=false
137 137
 
138
+# Specify the scheduler frequency in seconds (default: 0.3).
139
+# Increasing this value will help reduce the use of system resources
140
+# at the expense of time accuracy.
141
+#SCHEDULER_FREQUENCY=0.3
142
+
138 143
 # Use Graphviz for generating diagrams instead of using Google Chart
139 144
 # Tools.  Specify a dot(1) command path built with SVG support
140 145
 # enabled.

+ 21 - 19
app/assets/stylesheets/application.css.scss.erb

@@ -157,6 +157,7 @@ span.not-applicable:after {
157 157
   height: 16px;
158 158
   display: inline-block;
159 159
   vertical-align: inherit;
160
+  cursor: pointer;
160 161
 
161 162
   &.refresh {
162 163
     margin: 0 10px;
@@ -240,7 +241,22 @@ h2 .scenario, a span.label.scenario {
240 241
   width: 200px;
241 242
 }
242 243
 
243
-.btn-auth {
244
+$services:            twitter     37signals   github      tumblr      dropbox;
245
+$service-colors:      #55acee     #8fc857     #444444     #2c4762     #007EE5;
246
+
247
+@mixin services {
248
+  @each $service in $services {
249
+    $i: index($services, $service);
250
+    $service-color: nth($service-colors, $i);
251
+
252
+    &.service-#{$service} {
253
+      color: #fff;
254
+      background-color: $service-color;
255
+    }
256
+  }
257
+}
258
+
259
+.btn-service {
244 260
   position: relative;
245 261
   padding-left: 40px;
246 262
   $border-color: rgba(0,0,0,0.2);
@@ -259,23 +275,9 @@ h2 .scenario, a span.label.scenario {
259 275
     border-right: 1px solid $border-color;
260 276
   }
261 277
 
262
-  &.btn-auth-twitter {
263
-    color: #fff;
264
-    background-color: #55acee;
265
-  }
266
-
267
-  &.btn-auth-37signals {
268
-    color: #fff;
269
-    background-color: #8fc857;
270
-  }
271
-
272
-  &.btn-auth-github {
273
-    color: #fff;
274
-    background-color: #444;
275
-  }
278
+  @include services;
279
+}
276 280
 
277
-  &.btn-auth-dropbox {
278
-    color: #fff;
279
-    background-color: #007EE5;
280
-  }
281
+.label-service {
282
+  @include services;
281 283
 }

+ 34 - 9
app/concerns/agent_controller_concern.rb

@@ -12,11 +12,11 @@ module AgentControllerConcern
12 12
   end
13 13
 
14 14
   def control_action
15
-    options['action'].presence || 'run'
15
+    interpolated['action']
16 16
   end
17 17
 
18 18
   def validate_control_action
19
-    case control_action
19
+    case options['action']
20 20
     when 'run'
21 21
       control_targets.each { |target|
22 22
         if target.cannot_be_scheduled?
@@ -24,24 +24,49 @@ module AgentControllerConcern
24 24
         end
25 25
       }
26 26
     when 'enable', 'disable'
27
+    when nil
28
+      errors.add(:base, "action must be specified")
29
+    when /\{[%{]/
30
+      # Liquid template
27 31
     else
28 32
       errors.add(:base, 'invalid action')
29 33
     end
30 34
   end
31 35
 
32 36
   def control!
33
-    control_targets.active.each { |target|
37
+    control_targets.each { |target|
34 38
       begin
35 39
         case control_action
36 40
         when 'run'
37
-          log "Agent run queued for '#{target.name}'"
38
-          Agent.async_check(target.id)
41
+          case
42
+          when target.cannot_be_scheduled?
43
+            error "'#{target.name}' cannot run without an incoming event"
44
+          when target.disabled?
45
+            log "Agent run ignored for disabled Agent '#{target.name}'"
46
+          else
47
+            Agent.async_check(target.id)
48
+            log "Agent run queued for '#{target.name}'"
49
+          end
39 50
         when 'enable'
40
-          log "Enabling the Agent '#{target.name}'"
41
-          target.update!(disable: false) if target.disabled?
51
+          case
52
+          when target.disabled?
53
+            target.update!(disabled: false)
54
+            log "Agent '#{target.name}' is enabled"
55
+          else
56
+            log "Agent '#{target.name}' is already enabled"
57
+          end
42 58
         when 'disable'
43
-          log "Disabling the Agent '#{target.name}'"
44
-          target.update!(disable: true) unless target.disabled?
59
+          case
60
+          when target.disabled?
61
+            log "Agent '#{target.name}' is alread disabled"
62
+          else
63
+            target.update!(disabled: true)
64
+            log "Agent '#{target.name}' is disabled"
65
+          end
66
+        when ''
67
+          # Do nothing
68
+        else
69
+          error "Unsupported action '#{control_action}' ignored for '#{target.name}'"
45 70
         end
46 71
       rescue => e
47 72
         error "Failed to #{control_action} '#{target.name}': #{e.message}"

+ 1 - 1
app/controllers/application_controller.rb

@@ -1,7 +1,7 @@
1 1
 class ApplicationController < ActionController::Base
2 2
   protect_from_forgery
3 3
 
4
-  before_filter :authenticate_user!
4
+  before_action :authenticate_user!
5 5
   before_action :configure_permitted_parameters, if: :devise_controller?
6 6
 
7 7
   helper :all

+ 1 - 1
app/controllers/events_controller.rb

@@ -1,5 +1,5 @@
1 1
 class EventsController < ApplicationController
2
-  before_filter :load_event, :except => :index
2
+  before_action :load_event, except: :index
3 3
 
4 4
   def index
5 5
     if params[:agent_id]

+ 2 - 2
app/controllers/home_controller.rb

@@ -1,7 +1,7 @@
1 1
 class HomeController < ApplicationController
2
-  skip_before_filter :authenticate_user!
2
+  skip_before_action :authenticate_user!
3 3
 
4
-  before_filter :upgrade_warning, only: :index
4
+  before_action :upgrade_warning, only: :index
5 5
 
6 6
   def index
7 7
   end

+ 1 - 1
app/controllers/jobs_controller.rb

@@ -1,5 +1,5 @@
1 1
 class JobsController < ApplicationController
2
-  before_filter :authenticate_admin!
2
+  before_action :authenticate_admin!
3 3
 
4 4
   def index
5 5
     @jobs = Delayed::Job.order("coalesce(failed_at,'1000-01-01'), run_at asc").page(params[:page])

+ 1 - 1
app/controllers/logs_controller.rb

@@ -1,5 +1,5 @@
1 1
 class LogsController < ApplicationController
2
-  before_filter :load_agent
2
+  before_action :load_agent
3 3
 
4 4
   def index
5 5
     @logs = @agent.logs.all

+ 1 - 1
app/controllers/scenarios_controller.rb

@@ -1,6 +1,6 @@
1 1
 class ScenariosController < ApplicationController
2 2
   include SortableTable
3
-  skip_before_filter :authenticate_user!, :only => :export
3
+  skip_before_action :authenticate_user!, only: :export
4 4
 
5 5
   def index
6 6
     set_table_sort sorts: %w[name public], default: { name: :asc }

+ 1 - 1
app/controllers/services_controller.rb

@@ -1,7 +1,7 @@
1 1
 class ServicesController < ApplicationController
2 2
   include SortableTable
3 3
 
4
-  before_filter :upgrade_warning, only: :index
4
+  before_action :upgrade_warning, only: :index
5 5
 
6 6
   def index
7 7
     set_table_sort sorts: %w[provider name global], default: { provider: :asc }

+ 2 - 1
app/controllers/web_requests_controller.rb

@@ -16,7 +16,8 @@
16 16
 #   ["not found", 404, 'text/plain']
17 17
 
18 18
 class WebRequestsController < ApplicationController
19
-  skip_before_filter :authenticate_user!
19
+  skip_before_action :verify_authenticity_token
20
+  skip_before_action :authenticate_user!
20 21
 
21 22
   def handle_request
22 23
     user = User.find_by_id(params[:user_id])

+ 26 - 4
app/helpers/application_helper.rb

@@ -41,12 +41,34 @@ module ApplicationHelper
41 41
     end
42 42
   end
43 43
 
44
-  def icon_for_service(service)
45
-    case service.to_sym
44
+  def omniauth_provider_icon(provider)
45
+    case provider.to_sym
46 46
     when :twitter, :tumblr, :github, :dropbox
47
-      "<i class='fa fa-#{service}'></i>".html_safe
47
+      content_tag :i, '', class: "fa fa-#{provider}"
48 48
     else
49
-      "<i class='fa fa-lock'></i>".html_safe
49
+      content_tag :i, '', class: "fa fa-lock"
50 50
     end
51 51
   end
52
+
53
+  def omniauth_provider_name(provider)
54
+    t("devise.omniauth_providers.#{provider}")
55
+  end
56
+
57
+  def omniauth_button(provider)
58
+    link_to [
59
+      omniauth_provider_icon(provider),
60
+      content_tag(:span, "Authenticate with #{omniauth_provider_name(provider)}")
61
+    ].join.html_safe, user_omniauth_authorize_path(provider), class: "btn btn-default btn-service service-#{provider}"
62
+  end
63
+
64
+  def service_label_text(service)
65
+    "#{omniauth_provider_name(service.provider)} - #{service.name}"
66
+  end
67
+
68
+  def service_label(service)
69
+    content_tag :span, [
70
+      omniauth_provider_icon(service.provider),
71
+      service_label_text(service)
72
+    ].join.html_safe, class: "label label-default label-service service-#{service.provider}"
73
+  end
52 74
 end

+ 47 - 0
app/models/agents/commander_agent.rb

@@ -0,0 +1,47 @@
1
+module Agents
2
+  class CommanderAgent < Agent
3
+    include AgentControllerConcern
4
+
5
+    cannot_create_events!
6
+
7
+    description <<-MD
8
+      This agent is triggered by schedule or an incoming event and commands other agents ("targets") to run, disable or enable themselves.
9
+
10
+      # Action types
11
+
12
+      Set `action` to one of the action types below:
13
+
14
+      * `run`: Target Agents are run when this agent is triggered.
15
+
16
+      * `disable`: Target Agents are disabled (if not) when this agent is triggered.
17
+
18
+      * `enable`: Target Agents are enabled (if not) when this agent is triggered.
19
+
20
+      Here's a tip: you can use Liquid templating to dynamically determine the action type.  For example:
21
+
22
+      - To create a CommanderAgent that receives an event from WeatherAgent every morning to kick an agent flow that is only useful in a nice weather, try this: `{% if conditions contains 'Sunny' or conditions contains 'Cloudy' %}run{% endif %}`
23
+
24
+      - Likewise, if you have a scheduled agent flow specially crafted for rainy days, try this: `{% if conditions contains 'Rain' %}enable{% else %}disabled{% endif %}`
25
+
26
+      # Targets
27
+
28
+      Select Agents that you want to control from this CommanderAgent.
29
+    MD
30
+
31
+    def working?
32
+      true
33
+    end
34
+
35
+    def check!
36
+      control!
37
+    end
38
+
39
+    def receive(incoming_events)
40
+      incoming_events.each do |event|
41
+        interpolate_with(event) do
42
+          control!
43
+        end
44
+      end
45
+    end
46
+  end
47
+end

+ 1 - 1
app/models/agents/scheduler_agent.rb

@@ -19,7 +19,7 @@ module Agents
19 19
 
20 20
       Set `action` to one of the action types below:
21 21
 
22
-      * `run`: This is the default.  Target Agents are run at intervals.
22
+      * `run`: Target Agents are run at intervals, except for those disabled.
23 23
 
24 24
       * `disable`: Target Agents are disabled (if not) at intervals.
25 25
 

+ 1 - 1
app/views/agents/_oauth_dropdown.html.erb

@@ -1,6 +1,6 @@
1 1
 <% if agent.try(:oauthable?) %>
2 2
   <div class="form-group type-select">
3 3
     <%= label_tag :service %>
4
-    <%= select_tag 'agent[service_id]', options_for_select(agent.valid_services_for(current_user).collect { |s| ["(#{s.provider}) #{s.name}", s.id]}, agent.service_id), class: 'form-control' %>
4
+    <%= select_tag 'agent[service_id]', options_for_select(agent.valid_services_for(current_user).collect { |s| [service_label_text(s), s.id] }, agent.service_id), class: 'form-control' %>
5 5
   </div>
6 6
 <% end %>

+ 7 - 0
app/views/agents/show.html.erb

@@ -107,6 +107,13 @@
107 107
               </p>
108 108
             <% end %>
109 109
 
110
+            <% if @agent.try(:oauthable?) %>
111
+              <p>
112
+                <b>Service:</b>
113
+                <%= service_label(@agent.service) %>
114
+              </p>
115
+            <% end %>
116
+
110 117
             <% if @agent.can_receive_events? %>
111 118
               <p>
112 119
                 <b>Event sources:</b>

+ 0 - 6
app/views/devise/shared/_links.erb

@@ -17,9 +17,3 @@
17 17
 <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
18 18
   <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br />
19 19
 <% end -%>
20
-
21
-<%- if devise_mapping.omniauthable? %>
22
-  <%- resource_class.omniauth_providers.each do |provider| %>
23
-    <%= link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider) %><br />
24
-  <% end -%>
25
-<% end -%>

+ 3 - 3
app/views/services/index.html.erb

@@ -12,7 +12,7 @@
12 12
         for guidance.
13 13
       </p>
14 14
       <%- Devise.omniauth_providers.each { |provider| -%>
15
-        <p><%= link_to user_omniauth_authorize_path(provider), class: "btn btn-default btn-auth btn-auth-#{provider}" do %><%= icon_for_service(provider) %><span>Authenticate with <%= t("devise.omniauth_providers.#{provider}") %></span><% end %></p>
15
+        <p><%= omniauth_button(provider) %></p>
16 16
       <%- } -%>
17 17
       <hr>
18 18
 
@@ -27,9 +27,9 @@
27 27
 
28 28
         <% @services.each do |service| %>
29 29
           <tr>
30
-            <td><%= service.provider %></td>
30
+            <td><%= omniauth_provider_name(service.provider) %></td>
31 31
             <td><%= service.name %></td>
32
-            <td><%= service.global ? 'Yes' : 'No' %></td>
32
+            <td><%= yes_no(service.global) %></td>
33 33
             <td>
34 34
               <div class="btn-group btn-group-xs">
35 35
                 <% if service.global %>

+ 2 - 2
bin/schedule.rb

@@ -11,5 +11,5 @@ unless defined?(Rails)
11 11
   exit 1
12 12
 end
13 13
 
14
-scheduler = HuginnScheduler.new
15
-scheduler.run!
14
+scheduler = HuginnScheduler.new(frequency: ENV['SCHEDULER_FREQUENCY'])
15
+scheduler.run!

+ 1 - 1
bin/threaded.rb

@@ -33,7 +33,7 @@ end
33 33
 
34 34
 threads << Thread.new do
35 35
   safely do
36
-    @scheduler = HuginnScheduler.new
36
+    @scheduler = HuginnScheduler.new(frequency: ENV['SCHEDULER_FREQUENCY'])
37 37
     @scheduler.run!
38 38
     puts "Scheduler stopped ..."
39 39
   end

+ 2 - 2
lib/huginn_scheduler.rb

@@ -96,8 +96,8 @@ class HuginnScheduler
96 96
   FAILED_JOBS_TO_KEEP = 100
97 97
   attr_accessor :mutex
98 98
 
99
-  def initialize
100
-    @rufus_scheduler = Rufus::Scheduler.new
99
+  def initialize(options = {})
100
+    @rufus_scheduler = Rufus::Scheduler.new(options)
101 101
     self.mutex = Mutex.new
102 102
   end
103 103
 

+ 4 - 4
spec/helpers/application_helper_spec.rb

@@ -121,23 +121,23 @@ describe ApplicationHelper do
121 121
     end
122 122
   end
123 123
 
124
-  describe '#icon_for_service' do
124
+  describe '#omniauth_provider_icon' do
125 125
     it 'returns a correct icon tag for Twitter' do
126
-      icon = icon_for_service(:twitter)
126
+      icon = omniauth_provider_icon(:twitter)
127 127
       expect(icon).to be_html_safe
128 128
       elem = Nokogiri(icon).at('i.fa.fa-twitter')
129 129
       expect(elem).to be_a Nokogiri::XML::Element
130 130
     end
131 131
 
132 132
     it 'returns a correct icon tag for GitHub' do
133
-      icon = icon_for_service(:github)
133
+      icon = omniauth_provider_icon(:github)
134 134
       expect(icon).to be_html_safe
135 135
       elem = Nokogiri(icon).at('i.fa.fa-github')
136 136
       expect(elem).to be_a Nokogiri::XML::Element
137 137
     end
138 138
 
139 139
     it 'returns a correct icon tag for other services' do
140
-      icon = icon_for_service(:'37signals')
140
+      icon = omniauth_provider_icon(:'37signals')
141 141
       expect(icon).to be_html_safe
142 142
       elem = Nokogiri(icon).at('i.fa.fa-lock')
143 143
       expect(elem).to be_a Nokogiri::XML::Element

+ 2 - 2
spec/lib/huginn_scheduler_spec.rb

@@ -86,11 +86,11 @@ describe Rufus::Scheduler do
86 86
 
87 87
     stub.any_instance_of(Agents::SchedulerAgent).second_precision_enabled { true }
88 88
 
89
-    @agent1 = Agents::SchedulerAgent.new(name: 'Scheduler 1', options: { schedule: '*/1 * * * * *' }).tap { |a|
89
+    @agent1 = Agents::SchedulerAgent.new(name: 'Scheduler 1', options: { action: 'run', schedule: '*/1 * * * * *' }).tap { |a|
90 90
       a.user = users(:bob)
91 91
       a.save!
92 92
     }
93
-    @agent2 = Agents::SchedulerAgent.new(name: 'Scheduler 2', options: { schedule: '*/1 * * * * *' }).tap { |a|
93
+    @agent2 = Agents::SchedulerAgent.new(name: 'Scheduler 2', options: { action: 'run', schedule: '*/1 * * * * *' }).tap { |a|
94 94
       a.user = users(:bob)
95 95
       a.save!
96 96
     }

+ 42 - 0
spec/models/agents/commander_agent_spec.rb

@@ -0,0 +1,42 @@
1
+require 'spec_helper'
2
+
3
+describe Agents::CommanderAgent do
4
+  let(:valid_params) {
5
+    {
6
+      name: 'Example',
7
+      schedule: 'every_1h',
8
+      options: {
9
+        'action' => 'run',
10
+      },
11
+    }
12
+  }
13
+
14
+  let(:agent) {
15
+    described_class.create!(valid_params) { |agent|
16
+      agent.user = users(:bob)
17
+    }
18
+  }
19
+
20
+  it_behaves_like AgentControllerConcern
21
+
22
+  describe "check!" do
23
+    it "should command targets" do
24
+      stub(agent).control!.once { nil }
25
+      agent.check!
26
+    end
27
+  end
28
+
29
+  describe "receive_events" do
30
+    it "should command targets" do
31
+      stub(agent).control!.once { nil }
32
+
33
+      event = Event.new
34
+      event.agent = agents(:bob_rain_notifier_agent)
35
+      event.payload = {
36
+        'url' => 'http://xkcd.com',
37
+        'link' => 'Random',
38
+      }
39
+      agent.receive([event])
40
+    end
41
+  end
42
+end

+ 59 - 105
spec/models/agents/scheduler_agent_spec.rb

@@ -1,96 +1,68 @@
1 1
 require 'spec_helper'
2 2
 
3 3
 describe Agents::SchedulerAgent do
4
-  before do
5
-    @agent = Agents::SchedulerAgent.new(name: 'Example', options: { 'schedule' => '0 * * * *' })
6
-    @agent.user = users(:bob)
7
-    @agent.save
8
-  end
4
+  let(:valid_params) {
5
+    {
6
+      name: 'Example',
7
+      options: {
8
+        'action' => 'run',
9
+        'schedule' => '0 * * * *'
10
+      },
11
+    }
12
+  }
13
+
14
+  let(:agent) {
15
+    described_class.create!(valid_params) { |agent|
16
+      agent.user = users(:bob)
17
+    }
18
+  }
19
+
20
+  it_behaves_like AgentControllerConcern
9 21
 
10 22
   describe "validation" do
11
-    it "should validate action" do
12
-      ['run', 'enable', 'disable', '', nil].each { |action|
13
-        @agent.options['action'] = action
14
-        expect(@agent).to be_valid
15
-      }
16
-
17
-      ['delete', 1, true].each { |action|
18
-        @agent.options['action'] = action
19
-        expect(@agent).not_to be_valid
20
-      }
21
-    end
22
-
23 23
     it "should validate schedule" do
24
-      expect(@agent).to be_valid
24
+      expect(agent).to be_valid
25 25
 
26
-      @agent.options.delete('schedule')
27
-      expect(@agent).not_to be_valid
26
+      agent.options.delete('schedule')
27
+      expect(agent).not_to be_valid
28 28
 
29
-      @agent.options['schedule'] = nil
30
-      expect(@agent).not_to be_valid
29
+      agent.options['schedule'] = nil
30
+      expect(agent).not_to be_valid
31 31
 
32
-      @agent.options['schedule'] = ''
33
-      expect(@agent).not_to be_valid
32
+      agent.options['schedule'] = ''
33
+      expect(agent).not_to be_valid
34 34
 
35
-      @agent.options['schedule'] = '0'
36
-      expect(@agent).not_to be_valid
35
+      agent.options['schedule'] = '0'
36
+      expect(agent).not_to be_valid
37 37
 
38
-      @agent.options['schedule'] = '*/15 * * * * * *'
39
-      expect(@agent).not_to be_valid
38
+      agent.options['schedule'] = '*/15 * * * * * *'
39
+      expect(agent).not_to be_valid
40 40
 
41
-      @agent.options['schedule'] = '*/1 * * * *'
42
-      expect(@agent).to be_valid
41
+      agent.options['schedule'] = '*/1 * * * *'
42
+      expect(agent).to be_valid
43 43
 
44
-      @agent.options['schedule'] = '*/1 * * *'
45
-      expect(@agent).not_to be_valid
44
+      agent.options['schedule'] = '*/1 * * *'
45
+      expect(agent).not_to be_valid
46 46
 
47
-      stub(@agent).second_precision_enabled { true }
48
-      @agent.options['schedule'] = '*/15 * * * * *'
49
-      expect(@agent).to be_valid
47
+      stub(agent).second_precision_enabled { true }
48
+      agent.options['schedule'] = '*/15 * * * * *'
49
+      expect(agent).to be_valid
50 50
 
51
-      stub(@agent).second_precision_enabled { false }
52
-      @agent.options['schedule'] = '*/10 * * * * *'
53
-      expect(@agent).not_to be_valid
51
+      stub(agent).second_precision_enabled { false }
52
+      agent.options['schedule'] = '*/10 * * * * *'
53
+      expect(agent).not_to be_valid
54 54
 
55
-      @agent.options['schedule'] = '5/30 * * * * *'
56
-      expect(@agent).not_to be_valid
55
+      agent.options['schedule'] = '5/30 * * * * *'
56
+      expect(agent).not_to be_valid
57 57
 
58
-      @agent.options['schedule'] = '*/15 * * * * *'
59
-      expect(@agent).to be_valid
58
+      agent.options['schedule'] = '*/15 * * * * *'
59
+      expect(agent).to be_valid
60 60
 
61
-      @agent.options['schedule'] = '15,45 * * * * *'
62
-      expect(@agent).to be_valid
61
+      agent.options['schedule'] = '15,45 * * * * *'
62
+      expect(agent).to be_valid
63 63
 
64
-      @agent.options['schedule'] = '0 * * * * *'
65
-      expect(@agent).to be_valid
66
-    end
67
-  end
68
-
69
-  describe 'control_action' do
70
-    it "should be one of the supported values" do
71
-      ['run', '', nil].each { |action|
72
-        @agent.options['action'] = action
73
-        expect(@agent.control_action).to eq('run')
74
-      }
75
-
76
-      ['enable', 'disable'].each { |action|
77
-        @agent.options['action'] = action
78
-        expect(@agent.control_action).to eq(action)
79
-      }
80
-    end
81
-
82
-    it "cannot be 'run' if any of the control targets cannot be scheduled" do
83
-      expect(@agent.control_action).to eq('run')
84
-      @agent.control_targets = [agents(:bob_rain_notifier_agent)]
85
-      expect(@agent).not_to be_valid
86
-    end
87
-
88
-    it "can be 'enable' or 'disable' no matter if control targets can be scheduled or not" do
89
-      ['enable', 'disable'].each { |action|
90
-        @agent.options['action'] = action
91
-        @agent.control_targets = [agents(:bob_rain_notifier_agent)]
92
-        expect(@agent).to be_valid
93
-      }
64
+      agent.options['schedule'] = '0 * * * * *'
65
+      expect(agent).to be_valid
94 66
     end
95 67
   end
96 68
 
@@ -98,43 +70,25 @@ describe Agents::SchedulerAgent do
98 70
     it "should delete memory['scheduled_at'] if and only if options is changed" do
99 71
       time = Time.now.to_i
100 72
 
101
-      @agent.memory['scheduled_at'] = time
102
-      @agent.save
103
-      expect(@agent.memory['scheduled_at']).to eq(time)
73
+      agent.memory['scheduled_at'] = time
74
+      agent.save
75
+      expect(agent.memory['scheduled_at']).to eq(time)
104 76
 
105
-      @agent.memory['scheduled_at'] = time
106
-      # Currently @agent.options[]= is not detected
107
-      @agent.options = { 'schedule' => '*/5 * * * *' }
108
-      @agent.save
109
-      expect(@agent.memory['scheduled_at']).to be_nil
77
+      agent.memory['scheduled_at'] = time
78
+      # Currently agent.options[]= is not detected
79
+      agent.options = {
80
+        'action' => 'run',
81
+        'schedule' => '*/5 * * * *'
82
+      }
83
+      agent.save
84
+      expect(agent.memory['scheduled_at']).to be_nil
110 85
     end
111 86
   end
112 87
 
113 88
   describe "check!" do
114 89
     it "should control targets" do
115
-      control_targets = [agents(:bob_website_agent), agents(:bob_weather_agent)]
116
-      @agent.control_targets = control_targets
117
-      @agent.save!
118
-
119
-      control_target_ids = control_targets.map(&:id)
120
-      stub(Agent).async_check(anything) { |id|
121
-        control_target_ids.delete(id)
122
-      }
123
-
124
-      @agent.check!
125
-      expect(control_target_ids).to be_empty
126
-
127
-      @agent.options['action'] = 'disable'
128
-      @agent.save!
129
-
130
-      @agent.check!
131
-      control_targets.all? { |control_target| control_target.disabled? }
132
-
133
-      @agent.options['action'] = 'enable'
134
-      @agent.save!
135
-
136
-      @agent.check!
137
-      control_targets.all? { |control_target| !control_target.disabled? }
90
+      stub(agent).control!.once { nil }
91
+      agent.check!
138 92
     end
139 93
   end
140 94
 end

+ 111 - 0
spec/support/shared_examples/agent_controller_concern.rb

@@ -0,0 +1,111 @@
1
+require 'spec_helper'
2
+
3
+shared_examples_for AgentControllerConcern do
4
+  describe "preconditions" do
5
+    it "must be satisfied for these shared examples" do
6
+      expect(agent.user).to eq(users(:bob))
7
+      expect(agent.control_action).to eq('run')
8
+    end
9
+  end
10
+
11
+  describe "validation" do
12
+    describe "of action" do
13
+      it "should allow certain values" do
14
+        ['run', 'enable', 'disable', '{{ action }}'].each { |action|
15
+          agent.options['action'] = action
16
+          expect(agent).to be_valid
17
+        }
18
+      end
19
+
20
+      it "should disallow obviously bad values" do
21
+        ['delete', nil, 1, true].each { |action|
22
+          agent.options['action'] = action
23
+          expect(agent).not_to be_valid
24
+        }
25
+      end
26
+
27
+      it "should accept 'run' if all target agents are schedulable" do
28
+        agent.control_targets = [agents(:bob_website_agent)]
29
+        expect(agent).to be_valid
30
+      end
31
+
32
+      it "should reject 'run' if targets include an unschedulable agent" do
33
+        agent.control_targets = [agents(:bob_rain_notifier_agent)]
34
+        expect(agent).not_to be_valid
35
+      end
36
+
37
+      it "should not reject 'enable' or 'disable' no matter if targets include an unschedulable agent" do
38
+        ['enable', 'disable'].each { |action|
39
+          agent.options['action'] = action
40
+          agent.control_targets = [agents(:bob_rain_notifier_agent)]
41
+          expect(agent).to be_valid
42
+        }
43
+      end
44
+    end
45
+  end
46
+
47
+  describe 'control_action' do
48
+    it "returns options['action']" do
49
+      expect(agent.control_action).to eq('run')
50
+
51
+      ['run', 'enable', 'disable'].each { |action|
52
+        agent.options['action'] = action
53
+        expect(agent.control_action).to eq(action)
54
+      }
55
+    end
56
+
57
+    it "returns the result of interpolation" do
58
+      expect(agent.control_action).to eq('run')
59
+
60
+      agent.options['action'] = '{{ "enable" }}'
61
+      expect(agent.control_action).to eq('enable')
62
+    end
63
+  end
64
+
65
+  describe "control!" do
66
+    before do
67
+      agent.control_targets = [agents(:bob_website_agent), agents(:bob_weather_agent)]
68
+      agent.save!
69
+    end
70
+
71
+    it "should run targets" do
72
+      control_target_ids = agent.control_targets.map(&:id)
73
+      stub(Agent).async_check(anything) { |id|
74
+        control_target_ids.delete(id)
75
+      }
76
+
77
+      agent.control!
78
+      expect(control_target_ids).to be_empty
79
+    end
80
+
81
+    it "should not run disabled targets" do
82
+      control_target_ids = agent.control_targets.map(&:id)
83
+      stub(Agent).async_check(anything) { |id|
84
+        control_target_ids.delete(id)
85
+      }
86
+
87
+      agent.control_targets.last.update!(disabled: true)
88
+
89
+      agent.control!
90
+      expect(control_target_ids).to eq [agent.control_targets.last.id]
91
+    end
92
+
93
+    it "should enable targets" do
94
+      agent.options['action'] = 'disable'
95
+      agent.save!
96
+      agent.control_targets.first.update!(disabled: true)
97
+
98
+      agent.control!
99
+      expect(agent.control_targets.reload).to all(be_disabled)
100
+    end
101
+
102
+    it "should disable targets" do
103
+      agent.options['action'] = 'enable'
104
+      agent.save!
105
+      agent.control_targets.first.update!(disabled: true)
106
+
107
+      agent.control!
108
+      expect(agent.control_targets.reload).to all(satisfy { |a| !a.disabled? })
109
+    end
110
+  end
111
+end